package framework.security.token;

import framework.config.AESCryptoConfig;
import framework.crypto.AESCrypto;
import framework.crypto.AESCryptoPlus;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Random;

/**
 * 授权Token构建器
 */
@Slf4j
public class AuthTokenBuilder {

    @Getter
    private final AESCrypto crypto;

    public AuthTokenBuilder(String secret, int currentIndex) {
        if (!StringUtils.hasText(secret) || ",".equals(secret))
            throw new IllegalArgumentException("secret invalid");
        //
        AESCryptoConfig config = new AESCryptoConfig();
        config.setSecretIndex(currentIndex);
        config.setSecretKey(secret);
        this.crypto = this.createCrypto(config);
    }

    protected AESCrypto createCrypto(AESCryptoConfig config) {
        return new AESCryptoPlus(config);
    }

    private String encode(long userId, Date time, int duration, long sid) {
        if (time == null)
            throw new IllegalArgumentException("no set time");
        byte[] b1 = this.encodeV3(userId, time, duration, sid);
        String s = getCrypto().encodeUrlBase64(b1);
        return s;
    }

    public String encode(AuthTokenInfo authTokenInfo) {
        long l1 = authTokenInfo.getId();
        Date time = authTokenInfo.getExpireTime();
        int duration = authTokenInfo.getDuration();
        long sid = authTokenInfo.getSid();
        String s = this.encode(l1, time, duration, sid);
        return s;
    }

    public AuthTokenInfo decode(String encodeContent) {
        byte[] b1 = getCrypto().decodeUrlBase64(encodeContent);
        if (b1 == null) {
            return null;
        }
        if (b1.length == 37) {
            return this.decodeV3(b1);
        }
        if (b1.length == 33) {
            return this.decodeV2(b1);
        }
        if (b1.length == 31) {
            return this.decodeV0(b1);
        }
        if (b1.length == 39) {
            return this.decodeV1(b1);
        }
        return null;
    }

    private byte[] encodeV0(Long userId, Date time) {
        byte[] input = new byte[31];
        new Random().nextBytes(input);
        return ByteBuffer.wrap(input).putLong(9, userId).putLong(9 + 8, time.getTime()).array();
    }

    private AuthTokenInfo decodeV0(byte[] b1) {
        ByteBuffer buffer = ByteBuffer.wrap(b1);
        Long l1 = buffer.getLong(9);
        Long l2 = buffer.getLong(9 + 8);
        AuthTokenInfo authTokenInfo = new AuthTokenInfo();
        authTokenInfo.setId(l1);
        authTokenInfo.setExpireTime(new Date(l2));
        return authTokenInfo;
    }

    private byte[] encodeV1(Long userId, Date expireTime) {
        byte[] input = new byte[39];
        new Random().nextBytes(input);
        input[1] = 1; // version
        // rand + userid + expires time + create time + rand
        // 8 + 8 + 4 = 20
        long now = System.currentTimeMillis();
        long len = (expireTime.getTime() - now) / 1000;
        if (len > 365 * 86400) {
            throw new RuntimeException("expire time not than 365 days");
        }
        return ByteBuffer.wrap(input)
                .putLong(9, userId)
                .putLong(9 + 8, expireTime.getTime())
                .putLong(9 + 8 + 10, now)
                .array();
    }

    private byte[] encodeV2(Long userId, Date time, Integer duration) {
        byte[] input = new byte[33];
        new Random().nextBytes(input);
        int pos = Math.abs(input[0]) % 9;
        return ByteBuffer.wrap(input)
                .putLong(pos, userId)
                .putLong(pos + 8 + 2, time.getTime())
                .putInt(pos + 8 + 2 + 8 + 1, duration)
                .array();
    }

    private AuthTokenInfo decodeV2(byte[] b1) {
        ByteBuffer buffer = ByteBuffer.wrap(b1);
        int pos = Math.abs(b1[0]) % 9;
        Long l1 = buffer.getLong(pos);
        Long l2 = buffer.getLong(pos + 8 + 2);
        Integer l3 = buffer.getInt(pos + 8 + 2 + 8 + 1);
        AuthTokenInfo authTokenInfo = new AuthTokenInfo();
        authTokenInfo.setId(l1);
        authTokenInfo.setExpireTime(new Date(l2));
        authTokenInfo.setDuration(l3);
        return authTokenInfo;
    }

    private AuthTokenInfo decodeV1(byte[] b1) {
        ByteBuffer buffer = ByteBuffer.wrap(b1);
        Long id = buffer.getLong(9);
        Long expireTime = buffer.getLong(9 + 8);
        // Long createTime = buffer.getLong(9 + 8 + 10);
        //
        AuthTokenInfo authTokenInfo = new AuthTokenInfo();
        authTokenInfo.setId(id);
        authTokenInfo.setExpireTime(new Date(expireTime));
        // authTokenInfo.setCreateTime(new Date(createTime));
        //
        return authTokenInfo;
    }


    private byte[] encodeV3(long userId, Date time, int duration, long sid) {
        // require 8 + 8 + 4 + 8 = 28 + 8 = 36 < 37
        byte[] input = new byte[37];
        new Random().nextBytes(input);
        int pos = Math.abs(input[0]) % 9;
        return ByteBuffer.wrap(input)
                .putLong(pos, userId)
                .putLong(pos + 8, time.getTime())
                .putLong(pos + 8 + 8, sid)
                .putInt(pos + 8 + 8 + 8, duration)
                .array();
    }

    private AuthTokenInfo decodeV3(byte[] b1) {
        ByteBuffer buffer = ByteBuffer.wrap(b1);
        int pos = Math.abs(b1[0]) % 9;
        long u = buffer.getLong(pos);
        long t = buffer.getLong(pos + 8);
        long s = buffer.getLong(pos + 8 + 8);
        int d = buffer.getInt(pos + 8 + 8 + 8);
        AuthTokenInfo authTokenInfo = new AuthTokenInfo();
        authTokenInfo.setId(u);
        authTokenInfo.setExpireTime(new Date(t));
        authTokenInfo.setDuration(d);
        authTokenInfo.setSid(s);
        return authTokenInfo;
    }

}
